<?php

/**
 * 收藏(订阅)基础类
 *
 * @package    modules
 * @subpackage Favorite
 * @author     suning <snsnsky@gmail.com>
 */

namespace Favorite;

use Common\Bridge;

abstract class FavoriteBase
{
    /**
     * 日志路径
     */
    const LOG_FAIL_PATH = 'Favorite/Fail';

    /**
     * MC KEY, 用户下收藏列表
     */
    const CACHE_USER_ITEMS = 'F:Type_%d:User_%d';

    /**
     * MC KEY, 项目下用户列表
     */
    const CACHE_ITEM_USERS = 'F:Type_%d:Item_%d';

    /**
     * 表名/分表名
     */
    const DB_TABLE_NAME = 'Favorite';
    const DB_SHARD_TABLE = 'Favorite_%02x';

    /**
     * 收藏类型, 文章
     */
    const TYPE_ARTICLE = 1;
    /**
     * 收藏类型, 订阅标签
     */
    const TYPE_TAG = 2;
    /**
     * 收藏类型, 订阅标签
     */
    const TYPE_TATAHAO = 3;

    /**
     * 类型列表
     *
     * @var array
     */
    private static $_types = [
        self::TYPE_ARTICLE => 'Article',
        self::TYPE_TAG => 'Tag',
        self::TYPE_TATAHAO => 'TaTaHao',
    ];

    /**
     * 收藏当前分类
     *
     * @var int
     */
    private $_type;

    /**
     * 构造方法
     *
     * @param integer $type 类型, 见 self::$_types
     * @throws string       参数错误
     */
    public function __construct($type)
    {
        if (!isset(self::$_types[$type])) {
            throw new \Exception('参数错误: ' . $type);
        }

        $this->_type = $type;
    }

    /**
     * 更新用户收藏计数
     *
     * @param int    $userid 用户ID
     * @param string $count  计数变化， +1/-1
     * @return bool
     */
    abstract public function updateUserFavCount($userid, $count);

    /**
     * 更新项目收藏计数
     *
     * @param int    $itemid 项目ID
     * @param string $count  计数变化， +1/-1
     * @return bool
     */
    abstract public function updateItemFavCount($itemid, $count);

    /**
     * 添加收藏 (若已经收藏返回成功)
     *
     * @param integer $userid 用户ID
     * @param integer $itemid 项目ID
     * @return bool
     */
    public function add($userid, $itemid)
    {
        if ($this->isFavorite($userid, $itemid)) {
            return true;
        }

        $pdo = Bridge::pdo(Bridge::DB_FAVORITE_W);
        $data = [
            'UserId' => $userid,
            'ItemId' => $itemid,
            'ItemType' => $this->_type,
            'CreateTime' => date("Y-m-d H:i:s")
        ];

        $row_count = $pdo->insert(self::DB_TABLE_NAME, $data, ['ignore' => true]);
        $last_id = $pdo->lastInsertId();

        if ($row_count > 0 && $last_id > 0) {
            $pdo->insert(self::_shardTable($userid), array_merge(['FavoriteId' => $last_id], $data), ['ignore' => true]);

            // 更新收藏对象计数
            $this->updateItemFavCount($itemid, '+1');

            // 更新用户计数
            $this->updateUserFavCount($userid, '+1');
            $mc = Bridge::memcache(Bridge::MEMCACHE_DEFAULT);
            $mcdata = [];
            $mcdata[] = sprintf(self::CACHE_USER_ITEMS, $this->_type, $userid);
            $mcdata[] =  sprintf(self::CACHE_ITEM_USERS, $this->_type, $itemid);
            $mc->delete($mcdata);
        } elseif ($row_count === false) {
            runtime_log(self::LOG_FAIL_PATH, [
                'time' => date("Y-m-d H:i:s"),
                'from' => __CLASS__ . '->' . __METHOD__,
                'ret' => $row_count,
                'UserId' => $userid,
                'ItemId' => $itemid,
                'ItemType' => $this->_type
            ]);

            return false;
        }

        return true;
    }

    /**
     * 删除收藏 (未收藏时返回成功)
     *
     * @param integer $userid 用户ID
     * @param integer $itemid 项目ID
     * @return bool
     */
    public function remove($userid, $itemid)
    {
        if ($this->isFavorite($userid, $itemid)) {
            $pdo = Bridge::pdo(Bridge::DB_FAVORITE_W);
            $where = [
                'UserId' => $userid,
                'ItemId' => $itemid,
                'ItemType' => $this->_type,
            ];

            $rowcount1 = $pdo->delete(self::DB_TABLE_NAME, $where);
            $rowcount2 = $pdo->delete(self::_shardTable($userid), $where);

            if ($rowcount1 > 0 && $rowcount2 > 0) {
                // 更新收藏对象计数
                $this->updateItemFavCount($itemid, '-1');

                // 更新用户计数
                $this->updateUserFavCount($userid, '-1');

                $mc = Bridge::memcache(Bridge::MEMCACHE_DEFAULT);
                $mcdata = [];
                $mcdata[] = sprintf(self::CACHE_USER_ITEMS, $this->_type, $userid);
                $mcdata[] =  sprintf(self::CACHE_ITEM_USERS, $this->_type, $itemid);
                $mc->delete($mcdata);
            }

            if ($rowcount1 === false || $rowcount2 === false) {
                runtime_log(self::LOG_FAIL_PATH, [
                    'time' => date("Y-m-d H:i:s"),
                    'from' => __CLASS__ . '->' . __METHOD__,
                    'ret1' => $rowcount1,
                    'ret2' => $rowcount2,
                    'UserId' => $userid,
                    'ItemId' => $itemid,
                    'ItemType' => $this->_type
                ]);

                return false;
            }
        }

        return true;
    }

    /**
     * 判断一个或多个项目是否被用户收藏
     *
     * @param int       $userid
     * @param int|array $itemid
     * @return bool|array  一个时返回true/false, 多个是返回['ItemId1' => true, 'ItemId2' => false, ...]
     */
    public function isFavorite($userid, $itemid)
    {
        $items = $this->_getUserItems($userid);
        if (is_array($itemid)) {
            $ret = [];
            $tmp = [];
            foreach ($items as $v) {
                $tmp[$v] = $v;
            }
            foreach ($itemid as $v) {
                if (isset($tmp[$v])) {
                    $ret[$v] = true;
                } else {
                    $ret[$v] = false;
                }
            }

            return $ret;

        }

        return in_array($itemid, $items);
    }

    /**
     * 获取用户的收藏记录
     *
     * @param int $userid 用户ID
     * @param int $offset 偏移量
     * @param int $limit  每次数量
     * @return array          ['total' => 记录总数, 'data' => [ItemId1, ItemId2, ...]]
     */
    public function getItemsByUserId($userid, $offset = 0, $limit = 10)
    {
        $items = $this->_getUserItems($userid);
        $count = count($items);
        if ($offset !== -1) {
            $items = array_slice($items, $offset, $limit);
        }

        return ['total' => $count, 'data' => $items];
    }

    /**
     * 根据项目ID获取用户ID
     *
     * @param int $itemid 项目ID
     * @return array      用户ID [UserId1, UserId2, UserId3]
     */
    public function getUsersByItemId($itemid)
    {
        $unpack_data = [];
        $mc = Bridge::memcache(Bridge::MEMCACHE_DEFAULT);
        $key = sprintf(self::CACHE_ITEM_USERS, $this->_type, $itemid);
        $data = $mc->get($key);

        if (empty($data)) {
            $pdo = Bridge::pdo(Bridge::DB_FAVORITE_R);
            $ret = $pdo->find(self::DB_TABLE_NAME, [
                'ItemId' => $itemid,
                'ItemType' => $this->_type,
            ], 'UserId', 'FavoriteId ASC');
            $data = '';

            foreach ($ret as $v) {
                $data .= pack('L', $v['UserId']);
                $unpack_data[] = $v['UserId'];
            }
            $mc->set($key, $data, 86400);
        }

        if (count($unpack_data) > 0) {
            return $unpack_data;
        }

        $data = unpack('L*', $data);
        is_array($data) || $data = [];

        return $data;
    }

    private function _getUserItems($userid)
    {
        $unpack_data = [];
        $mc = Bridge::memcache(Bridge::MEMCACHE_DEFAULT);
        $key = sprintf(self::CACHE_USER_ITEMS, $this->_type, $userid);
        $data = $mc->get($key);
        if (empty($data)) {
            $pdo = Bridge::pdo(Bridge::DB_FAVORITE_R);
            $ret = $pdo->find(self::_shardTable($userid), [
                'UserId' => $userid,
                'ItemType' => $this->_type,
            ], 'ItemId', 'FavoriteId DESC');
            $data = '';

            foreach ($ret as $v) {
                $data .= pack('L', $v['ItemId']);
                $unpack_data[] = $v['ItemId'];
            }
            $mc->set($key, $data, 86400);
        }
        if (count($unpack_data) > 0) {
            return $unpack_data;
        }

        $data = unpack('L*', $data);
        is_array($data) || $data = [];

        return $data;
    }

    private static function _shardTable($id)
    {
        return sprintf(self::DB_SHARD_TABLE, $id % 256);
    }
}